﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace MidasGtsExporter.FLAC3D
{
	public class Flac3DExporter
	{
		#region private fields

		private SortedList<int, Node> _nodeList;

		private SortedList<int, Elem> _elem3DList;
		private SortedList<int, Elem> _elem2DList;
		private SortedList<int, Elem> _elem1DList;

		private SortedList<int, List<int>> _elem3DGroups;
		private SortedList<int, List<int>> _elem2DGroups;
		private SortedList<int, List<int>> _elem1DGroups;

		private Dictionary<int, string> _elem3DGroupNames;
		private Dictionary<int, string> _elem2DGroupNames;
		private Dictionary<int, string> _elem1DGroupNames;

		#endregion

		#region public properties

		public string InputFilePath { get; set; }
		public string OutputFolderPath { get; set; }
		public string OutputFileName { get; set; } = "Flac3dMesh"; // File extension exclude

		public Flac3dOption.Option Option { get; set; }

		#endregion

		public void DoExport(BackgroundWorker backgroundWorker)
		{
			Debug.Assert(IO.IsFileExist(InputFilePath));

			//
			// Parse Midas GTS FPN file
			//
			backgroundWorker.ReportProgress(10, "解析FPN文件内容 ......");

			var gtsReader = new GtsFpnDataReader();
			gtsReader.ReadData(InputFilePath);

			_nodeList = gtsReader.NodeList;
			_elem3DList = gtsReader.Elem3DList;
			_elem2DList = gtsReader.Elem2DList;
			_elem1DList = gtsReader.Elem1DList;
			Debug.Assert(_nodeList != null && _elem3DList != null
			             && _elem2DList != null && _elem1DList != null);
			if (_nodeList.Count <= 0 || (_elem3DList.Count <= 0) && (_elem2DList.Count <= 0))
			{
				backgroundWorker.ReportProgress(100, "*** FPN文件中无有效节点和单元数据.");
				return;
			}

			//
			// Convert mesh data
			//
			backgroundWorker.ReportProgress(30, "转换网格数据 ......");

			_elem3DGroups = gtsReader.Elem3DGroups;
			_elem2DGroups = gtsReader.Elem2DGroups;
			_elem1DGroups = gtsReader.Elem1DGroups;
			Debug.Assert(_elem3DGroups != null && _elem2DGroups != null && _elem1DGroups != null);

			var elemGroupNames = gtsReader.ElemGroupNames;
			_elem3DGroupNames = _elem3DGroups
				.ToDictionary(kv => kv.Key, kv => $"eset_{elemGroupNames[kv.Key]}");
			_elem2DGroupNames = _elem2DGroups
				.ToDictionary(kv => kv.Key, kv => $"eset_{elemGroupNames[kv.Key]}");
			_elem1DGroupNames = _elem1DGroups
				.ToDictionary(kv => kv.Key, kv => $"eset_{elemGroupNames[kv.Key]}");

			//
			// Check node and element data
			//
			if (Option.CheckInputData)
			{
				backgroundWorker.ReportProgress(50, "检查节点和单元数据 ......");
				/*

				*/
			}

			//
			// Output mesh data
			//
			backgroundWorker.ReportProgress(70, "输出FLAC3D网格数据 ......");

			CreateFlac3DGridFile(backgroundWorker);
			CreateFlac3DGeomFile(backgroundWorker);

			//
			// Output statistical information
			//
			{
				backgroundWorker.ReportProgress(90, " ****** 输出数据统计 ****** ");

				//
				var nodeCount = _nodeList.Count;
				var elem3DCount = _elem3DGroups?.Sum(g => g.Value.Count) ?? 0;
				var elem2DCount = _elem2DGroups?.Sum(g => g.Value.Count) ?? 0;
				var elem1DCount = _elem1DGroups?.Sum(g => g.Value.Count) ?? 0;
				var elemCount = elem3DCount + elem2DCount + elem1DCount;

				var newLine = Environment.NewLine;
				backgroundWorker.ReportProgress(91, $"节点数量：{nodeCount}");

				backgroundWorker.ReportProgress(92, $"单元数量：{elemCount}");
				if (_elem3DGroups != null && _elem3DGroups.Count > 0)
				{
					backgroundWorker.ReportProgress(93, $"3D单元数量：{elem3DCount}");
					backgroundWorker.ReportProgress(94,
						string.Join(newLine, _elem3DGroups.Select(g => $"    {_elem3DGroupNames[g.Key]}: {g.Value.Count}")));
				}
				if (_elem2DGroups != null && _elem2DGroups.Count > 0)
				{
					backgroundWorker.ReportProgress(95, $"2D单元数量：{elem2DCount}");
					backgroundWorker.ReportProgress(96,
						string.Join(newLine, _elem2DGroups.Select(g => $"    {_elem2DGroupNames[g.Key]}: {g.Value.Count}")));
				}
				if (_elem1DGroups != null && _elem1DGroups.Count > 0)
				{
					backgroundWorker.ReportProgress(97, $"1D单元数量：{elem1DCount}");
					backgroundWorker.ReportProgress(98,
						string.Join(newLine, _elem1DGroups.Select(g => $"    {_elem1DGroupNames[g.Key]}: {g.Value.Count}")));
				}
			}
		}

		private void CreateFlac3DGridFile(BackgroundWorker backgroundWorker)
		{
			if (_nodeList == null || _nodeList.Count <= 0 || (_elem3DGroups == null && _elem2DGroups == null))
				return;

			var content = new List<string>()
			{
				"*",
				$"* FLAC3D Mesh generated by MidasGtsExporter v{AppInfo.Version}, {DateTime.Now}",
				"*",
			};

			//
			// node definition
			//
			content.Add("* GRIDPOINTS");

			var allElemIds = _elem3DList.SelectMany(kv => kv.Value.NodeIds)
				.Concat(_elem2DList.SelectMany(kv => kv.Value.NodeIds));
			var outputNodeIds = allElemIds.Distinct();
			content.AddRange(outputNodeIds.Select(nodeId =>
			{
				var node = _nodeList[nodeId];
				var id = node.Id;
				var x = node.X;
				var y = node.Y;
				var z = node.Z;
				return $"G {id} {x:E6} {y:E6} {z:E6}";
			}));

			//
			// zone definition
			//
			if (_elem3DList != null && _elem3DList.Count > 0)
			{
				string GenElemLine(string pre, Elem elem, IEnumerable<int> idIndex)
				{
					return $"{pre} {elem.Id} " + string.Join(" ", idIndex.Select(i => $"{elem.NodeIds[i - 1]}"));
				}

				//
				// Output zone data
				//
				content.Add("* ZONES");
				var elems = _elem3DList;

				var sourceNodeCount = 8;
				var prefix = "Z B8";
				var nodeIdIndex = new[] {1, 2, 4, 5, 3, 8, 6, 7};
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(prefix, kv.Value, nodeIdIndex)));

				sourceNodeCount = 6;
				prefix = "Z W6";
				nodeIdIndex = new[] {1, 3, 4, 2, 6, 5};
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(prefix, kv.Value, nodeIdIndex)));

				sourceNodeCount = 5;
				prefix = "Z P5";
				nodeIdIndex = new[] {1, 2, 4, 5, 3};
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(prefix, kv.Value, nodeIdIndex)));

				sourceNodeCount = 4;
				prefix = "Z T4";
				nodeIdIndex = new[] {1, 2, 3, 4};
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(prefix, kv.Value, nodeIdIndex)));
			}

			//
			// Zone group data
			//
			if (_elem3DGroups != null && _elem3DGroups.Count > 0)
			{
				content.Add("* ZONE GROUPS");
				foreach (var groupData in _elem3DGroups)
				{
					var groupName = _elem3DGroupNames[groupData.Key];
					content.Add($"ZGROUP \"{groupName}\" SLOT 1 ");
					const int idCountPerLine = 20;
					var idLines = groupData.Value
						.GetSlices(idCountPerLine)
						.Select(ids => string.Join(" ", ids.Select(id => $"{id}")));
					content.AddRange(idLines);
				}
			}

			//
			// face definition
			//
			if (_elem2DList != null && _elem2DList.Count > 0)
			{
				string GenElemLine(string pre, Elem elem, IEnumerable<int> idIndex)
				{
					return $"{pre} {elem.Id} " + string.Join(" ", idIndex.Select(i => $"{elem.NodeIds[i - 1]}"));
				}

				//
				// face data
				//
				content.Add("* FACES");
				var elems = _elem2DList;

				var sourceNodeCount = 4;
				var prefix = "F Q4";
				var nodeIdIndex = new[] {1, 2, 3, 4};
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(prefix, kv.Value, nodeIdIndex)));

				sourceNodeCount = 3;
				prefix = "F T3";
				nodeIdIndex = new[] {1, 2, 3};
				content.AddRange(elems
					.Where(kv => kv.Value.NodeIds.Count == sourceNodeCount)
					.Select(kv => GenElemLine(prefix, kv.Value, nodeIdIndex)));
			}

			//
			// face group data
			//
			if (_elem2DGroups != null && _elem2DGroups.Count > 0)
			{
				content.Add("* FACE GROUPS");
				foreach (var groupData in _elem2DGroups)
				{
					var groupName = _elem2DGroupNames[groupData.Key];
					content.Add($"FGROUP \"{groupName}\" SLOT 2 ");
					const int idCountPerLine = 20;
					var idLines = groupData.Value
						.GetSlices(idCountPerLine)
						.Select(ids => string.Join(" ", ids.Select(id => $"{id}")));
					content.AddRange(idLines);
				}
			}

			//
			// Write to file
			//
			var filePath = Path.Combine(OutputFolderPath, $"{OutputFileName}_Mesh.f3grid");
			backgroundWorker.ReportProgress(80, $"Creating file: {filePath}");
			File.WriteAllLines(filePath, content);
		}

		private void CreateFlac3DGeomFile(BackgroundWorker backgroundWorker)
		{
			if (_nodeList == null || _nodeList.Count <= 0
			    || _elem1DList == null || _elem1DList.Count <= 0
			    || _elem1DGroups == null || _elem1DGroups.Count <= 0)
				return;

			var contents = new List<string>()
			{
				"ITASCA GEOMETRY3D",
				";",
			};

			//
			// Output nodes
			//
			contents.Add("NODES");
			var outputNodeIds = _elem1DList.SelectMany(elem => elem.Value.NodeIds);
			foreach (var nodeId in outputNodeIds)
			{
				var node = _nodeList[nodeId];
				var id = node.Id;
				var x = node.X;
				var y = node.Y;
				var z = node.Z;
				contents.Add($"{id} ({x:E6},{y:E6},{z:E6})");
			}

			//
			// Output edges
			//
			contents.Add("EDGES");
			foreach (var groupData in _elem1DGroups)
			{
				var groupId = groupData.Key;
				var groupName = _elem1DGroupNames[groupId];
				contents.Add($"GROUP \"Geom\" \"{groupName}\"");
				foreach (var elemId in groupData.Value)
				{
					var elem = _elem1DList[elemId];
					Debug.Assert(elem.NodeIds.Count == 2);
					contents.Add($"{elem.Id} {elem.NodeIds[0]} {elem.NodeIds[1]}");
				}
			}

			//
			// Write to file
			//
			var filePath = Path.Combine(OutputFolderPath, $"{OutputFileName}_Geom.geom");
			backgroundWorker.ReportProgress(90, $"Creating file: {filePath}");
			File.WriteAllLines(filePath, contents);
		}
	}
}